require 'open3'
require 'uri'
require 'rubygems'
require 'UPnP/service'
require 'UPnP/service/content_directory_item'
require 'UPnP/service/mythtv_content'
require 'webrick/httputils'

require 'UPnP/service/HTTPMythHandler'
##
# A UPnP ContentDirectory service.  See upnp.org for specifications.

class UPnP::Service::ContentDirectory < UPnP::Service

  VERSION = '1.0'

  ##
  # Returns the searching capabilities supported by the device

  add_action 'GetSearchCapabilities',
    [OUT, 'SearchCaps', 'SearchCapabilities']

  ##
  # Returns the CSV list of metadata tags that can be used in sortCriteria

  add_action 'GetSortCapabilities',
    [OUT, 'SortCaps', 'SortCapabilities']

  add_action 'GetSystemUpdateID',
    [OUT, 'Id', 'SystemUpdateID']

  add_action 'Browse',
    [IN, 'ObjectID',       'A_ARG_TYPE_ObjectID'],
    [IN, 'BrowseFlag',     'A_ARG_TYPE_BrowseFlag'],
    [IN, 'Filter',         'A_ARG_TYPE_Filter'],
    [IN, 'StartingIndex',  'A_ARG_TYPE_Index'],
    [IN, 'RequestedCount', 'A_ARG_TYPE_Count'],
    [IN, 'SortCriteria',   'A_ARG_TYPE_SortCriteria'],

    [OUT, 'Result',         'A_ARG_TYPE_Result'],
    [OUT, 'NumberReturned', 'A_ARG_TYPE_Count'],
    [OUT, 'TotalMatches',   'A_ARG_TYPE_Count'],
    [OUT, 'UpdateID',       'A_ARG_TYPE_UpdateID']

#  # optional actions
#
#  add_action 'Search',
#    [IN, 'ContainerID', 'A_ARG_TYPE_ObjectID'],
#    [IN, 'SearchCriteria', 'A_ARG_TYPE_SearchCriteria'],
#    [IN, 'Filter', 'A_ARG_TYPE_Filter'],
#    [IN, 'StartingIndex', 'A_ARG_TYPE_Index'],
#    [IN, 'RequestedCount', 'A_ARG_TYPE_Count'],
#    [IN, 'SortCriteria', 'A_ARG_TYPE_SortCriteria'],
#
#    [OUT, 'Result', 'A_ARG_TYPE_Result'],
#    [OUT, 'NumberReturned', 'A_ARG_TYPE_Count'],
#    [OUT, 'TotalMatches', 'A_ARG_TYPE_Count'],
#    [OUT, 'UpdateID', 'A_ARG_TYPE_UpdateID']
#
#  add_action 'CreateObject',
#    [IN, 'ContainerID', 'A_ARG_TYPE_ObjectID'],
#    [IN, 'Elements', 'A_ARG_TYPE_Result'],
#
#    [OUT, 'ObjectID', 'A_ARG_TYPE_ObjectID'],
#    [OUT, 'Result', 'A_ARG_TYPE_Result']
#
#  add_action 'DestroyObject',
#    [IN, 'ObjectID', 'A_ARG_TYPE_ObjectID']
#
#  add_action 'UpdateObject',
#    [IN, 'ObjectID', 'A_ARG_TYPE_ObjectID'],
#    [IN, 'CurrentTagValue', 'A_ARG_TYPE_TagValueList'],
#    [IN, 'NewTagValue', 'A_ARG_TYPE_TagValueList']
#
#  add_action 'ImportResource',
#    [IN, 'SourceURI', 'A_ARG_TYPE_URI'],
#    [IN, 'DestinationURI', 'A_ARG_TYPE_URI'],
#
#    [OUT, 'TransferID', 'A_ARG_TYPE_TransferID']
#
#  add_action 'ExportResource',
#    [IN, 'SourceURI', 'A_ARG_TYPE_URI'],
#    [IN, 'DestinationURI', 'A_ARG_TYPE_URI'],
#
#    [OUT, 'TransferID', 'A_ARG_TYPE_TransferID']
#
#  add_action 'StopTransferResource',
#    [IN, 'TransferID', 'A_ARG_TYPE_TransferID']
#
#  add_action 'GetTransferProgress',
#    [IN, 'TransferID', 'A_ARG_TYPE_TransferID'],
#
#    [OUT, 'TransferStatus', 'A_ARG_TYPE_TransferStatus'],
#    [OUT, 'TransferLength', 'A_ARG_TYPE_TransferLength'],
#    [OUT, 'TransferTotal', 'A_ARG_TYPE_TransferTotal']
#
#  add_action 'DeleteResource',
#    [IN, 'ResourceURI', 'A_ARG_TYPE_URI']
#
#  add_action 'CreateReference',
#    [IN, 'ContainerID', 'A_ARG_TYPE_ObjectID'],
#    [IN, 'ObjectID', 'A_ARG_TYPE_ObjectID'],
#    [OUT, 'NewID', 'A_ARG_TYPE_ObjectID']

  add_variable 'TransferIDs',               'string', nil, nil, true
  add_variable 'A_ARG_TYPE_ObjectID',       'string'
  add_variable 'A_ARG_TYPE_Result',         'string' # 2.5.4 - DIDL-Lite
  add_variable 'A_ARG_TYPE_SearchCriteria', 'string' # 2.5.5
  add_variable 'A_ARG_TYPE_BrowseFlag',     'string',
               %w[BrowseMetadata BrowseDirectChildren]
  add_variable 'A_ARG_TYPE_Filter',         'string' # 2.5.7
  add_variable 'A_ARG_TYPE_SortCriteria',   'string' # 2.5.8
  add_variable 'A_ARG_TYPE_Index',          'ui4'    # 2.5.9
  add_variable 'A_ARG_TYPE_Count',          'ui4'    # 2.5.10
  add_variable 'A_ARG_TYPE_UpdateID',       'ui4'    # 2.5.11
  add_variable 'A_ARG_Type_TransferID',     'ui4'    # 2.5.12
  add_variable 'A_ARG_Type_TransferStatus', 'string' # 2.5.13
  add_variable 'A_ARG_Type_TransferLength', 'string' # 2.5.14
  add_variable 'A_ARG_Type_TransferTotal',  'string' # 2.5.15
  add_variable 'A_ARG_TYPE_TagValueList',   'string' # 2.5.16
  add_variable 'A_ARG_TYPE_URI',            'uri'    # 2.5.17
  add_variable 'SearchCapabilities',        'string' # 2.5.18
  add_variable 'SortCapabilities',          'string' # 2.5.19
  add_variable 'SystemUpdateID',            'ui4',    nil, nil, true # 2.5.20
  add_variable 'ContainerUpdateIDs',        'string', nil, nil, true # 2.5.21

  def initialize(*args)
    @directories = []
    @SystemUpdateID = 0
    @rootContainer = UPnP::Service::Container.root_container

    @mime_types = WEBrick::HTTPUtils::DefaultMimeTypes

    @mime_types.update({'mkv' => 'video/mpeg', 
                  'm2ts'  => 'vedeo/mpeg',
                  'ts' => 'video/mpeg',
                  'avi' => 'video/mpeg'})
  
    super
  end

  # :section: ContentServer implementation

  ##
  # Allows the caller to incrementally browse the hierarchy of the content
  # directory, including information listing the classes of objects available
  # in any container.

  def Browse(object_id, browse_flag, filter, starting_index, requested_count,
             sort_criteria)

    object_id = object_id.to_i
    filter = filter.split ','
    starting_index = starting_index.to_i
    requested_count = requested_count.to_i
    update_id = 0

    case browse_flag
    when 'BrowseDirectChildren' then
      number_returned, total_matches, result = children_result(object_id, starting_index, requested_count)
    when 'BrowseMetadata' then
      number_returned = total_matches = 1

      result = make_result {|xml| @rootContainer[object_id].to_xml(xml)}
    else
      raise Error.new("Unknown BrowseFlag #{browse_flag}")
    end

    [nil, result, number_returned, total_matches, update_id]
  end

  ##
  # Returns the current value of the SystemUpdateID state variable.  For use
  # by clients that want to poll for any changes in the content directory
  # instead of subscribing to events.

  def GetSystemUpdateID
    [nil, @SystemUpdateID]
  end

  ##
  # Adds +directory+ as a path searched by the content server

  def add_directory(directory)
    populate_catalog(@rootContainer, directory)

    self
  end

  ##
  # Adds MythTv folder as  a folder under root connecting to the specified database
  def add_mythtv(icon_local_path, *myth_db)
    myth = MythContainer.new('MythTV', icon_local_path, *myth_db)
    @rootContainer << myth

    myth.update service_path

    self
  end

  #inserts folde/file into the container, returns its size
  #att: recursive function, no kidding. Prise its beauty
  def populate_catalog(container, f)

    if File.directory? f
      c = StorageFolder.new(File.basename(f))
      c.file = f
      container << c
      
      size = 0
      Dir.glob(File.join(f, '*')).each do |o| 
         size += populate_catalog(c, o)
      end
      c.storageUsed = size
    else
      #TODO: adjust it to detect type, size and other for files

      WEBrick::HTTPUtils.mime_type(f, @mime_types) =~ %r[^\w+]
      
      case $&
      when 'audio' then
        klass = AudioItem 
      when 'image' then
        klass = Photo 
      when 'video' then
        klass = VideoItem
      when 'text' then
        klass = TextItem
      else
        return 0
      end
      
      i = klass.new(File.basename(f, File.extname(f)))
      i.file = f
      container << i

      attributes, url, size = resource(i)
      i.res attributes, url 
      size
    end
  end
  
  def children_result(object_id, start_index, max_return)
    object = @rootContainer[object_id]

    if not object.kind_of? Container
      #TODO: return error code 710 - No such container
      #The specified ContainerID is invalid or identifies an object that is not a container.
      raise Error.new("Object '#{object.title}' must be a container to get its children list (710)")
    end

    if max_return <= 0 then
      ##all items, range is inclusive, since it does not metter
      r = Range.new(start_index, object.childCount)
    else
      ##range is exclusive '...'
      r = Range.new(start_index, start_index + max_return, true)
    end

    count = 0
    result = make_result do |xml|
      object.sort[r].each {|o| o.to_xml(xml); count += 1}
    end

    [count, object.childCount, result]
  end

  def make_result
    result = []

    xml = Builder::XmlMarkup.new :indent => 2, :target => result, :encoding=>"utf-8"
    xml.instruct! :xml, :version=>"1.0", :encoding=>"utf-8"
    xml.tag! 'DIDL-Lite',
             'xmlns' => 'urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/',
             'xmlns:dc' => 'http://purl.org/dc/elements/1.1/',
             'xmlns:upnp' => 'urn:schemas-upnp-org:metadata-1-0/upnp/' do
      yield xml
    end

    result.join
  end

  def mount_extra(http_server)
    super

    @rootContainer.each do |container|

      case container
      when MythContainer
        #mythtv uri is used ot handle myth request as opposed ot the file hanlder folders htat are 
        #respect conteainer ids 
        path = File.join(service_path, 'mythtv')
        http_server.mount path, UPnP::Service::HTTPMythHandler #options if needed
      else 
        path = File.join(service_path, container.id.to_s)
        http_server.mount path, WEBrick::HTTPServlet::FileHandler, container.file
      end
    end
  end

  def resource(item)

    begin
      size = File.size(item.file)
    rescue
      puts "can't read file size for #{item.file}"
      size = 0 
    end

    additional = "*"
     
    mime_type = WEBrick::HTTPUtils.mime_type(item.file, @mime_types)
      
    case mime_type
    when /^audio\/(.*)/ then
      pn = case $1
           when 'mpeg' then
             'MP3'
           end

      pn = "DLNA.ORG_PN=#{pn}"

      additional = [pn, 'DLNA.ORG_OP=01', 'DLNA.ORG_CI=0'].compact.join ';'

    when /^image\/(.*)/ then
      pn = case $1
           when 'jpeg' then
             'JPEG_LRG'
           end

      pn = "DLNA.ORG_PN=#{pn}"

      additional = [pn, 'DLNA.ORG_OP=01', 'DLNA.ORG_CI=0'].compact.join ';'

    end

    url = resource_url item

    attributes = {:protocolInfo => "http-get:*:#{mime_type}:#{additional}", 
                  :size => size}

    return attributes, URI.escape(url), size
  end

  ##
  # A URL to this object on this server.  Correctly handles multi-homed
  # servers.

  def resource_url(item)
    rootDir = item
    
    rootDir = @rootContainer[rootDir.parentId] while rootDir.parentId > 0 

    puts 
    
    File.join "http://$$server_host_port$$", service_path, rootDir.id.to_s, item.file.sub(rootDir.file, '')
  end
end
